package scene.accel.bsp;

import java.util.ArrayList;
import java.util.List;

import rasterization.Rasterizer;
import raytracing.Ray;
import scene.shape.geometry.Plane;
import scene.shape.geometry.Triangle;
import util.Vector3f;

public class BSPNode {
	
	protected Triangle triangle;
	protected BSPNode negative, positive;
	private Plane plane;
	
	Vector3f a, bMinACrossCMinA;
	
	private static final float EPS = 0.00885f;

	public BSPNode(Triangle t) {
		this.a = t.v1;
		Vector3f b = t.v2, c = t.v3;
		this.bMinACrossCMinA = (b.subtract(a)).cross(c.subtract(a));
		this.plane = new Plane(a, t.getSurfaceNormal());
		this.triangle = t;
	}

	public void add(Triangle T) {
		float fa = implicit(T.v1),
			  fb = implicit(T.v2),
			  fc = implicit(T.v3);
		if(Math.abs(fa) < EPS)
			fa = 0;
		if(Math.abs(fb) < EPS)
			fb = 0;
		if(Math.abs(fc) < EPS)
			fc = 0;
		if(fa <= 0 && fb <= 0 && fc <= 0){
			if(negative == null){
				if(T.area() != 0)
					negative = new BSPNode(T);
			}else{
				if(T.area() != 0)
					negative.add(T);
			}
		}else if(fa >= 0 && fb >= 0 && fc >= 0){
			if(positive == null){
				if(T.area() != 0)
					positive = new BSPNode(T);
			}else{
				if(T.area() != 0)
					positive.add(T);
			}
		}else{
			Vector3f a = T.v1, b = T.v2, c = T.v3;
			if(fa * fc >= 0){
				swap(fb, fc);
				swap(b,c);
				swap(fa,fb);
				swap(a,b);
			}
			else if(fb * fc >= 0){
				swap(fa,fc);
				swap(a,c);
				swap(fa,fb);
				swap(a,b);
			}
			Vector3f A = computeIntersection(a, c),
					 B = computeIntersection(b, c);
			Triangle t1 = new Triangle(a,b,A),
					 t2 = new Triangle(b,B,A),
					 t3 = new Triangle(A,B,c);
			t1.setShape(triangle.getShape());
			t2.setShape(triangle.getShape());
			t3.setShape(triangle.getShape());
			if(fc >= 0){
				if(negative == null){
					if(t1.area() != 0 && t2.area() != 0){
						negative = new BSPNode(t1);
						negative.add(t2);
					}else if(t1.area() == 0 && t2.area() != 0)
						negative = new BSPNode(t2);
					else
						negative = new BSPNode(t1);
				}else{
					if(t1.area() != 0)
						negative.add(t1);
					if(t2.area() != 0)
						negative.add(t2);
				}
				if(positive == null){
					if(t3.area() != 0)
						positive = new BSPNode(t3);
				}else{
					if(t3.area() != 0)
						positive.add(t3);
				}
			}else{
				if(positive == null){
					if(t1.area() != 0 && t2.area() != 0){
						positive = new BSPNode(t1);
						positive.add(t2);
					}else if(t1.area() == 0 && t2.area() != 0)
						positive = new BSPNode(t2);
					else
						positive = new BSPNode(t1);
				}else{
					if(t1.area() != 0)
						positive.add(t1);
					if(t2.area() != 0)
						positive.add(t2);
				}
				if(negative == null){
					if(t3.area() != 0)
						negative = new BSPNode(t3);
				}else{
					if(t3.area() != 0)
						negative.add(t3);
				}
			}
		}
	}
	
	private boolean exactlyOneZero(float fa, float fb, float fc) {
		return (fa == 0f && fb != 0f && fc != 0f)
				|| (fa != 0f && fb == 0f && fc != 0f)
				|| (fa != 0f && fb != 0f && fc == 0f);
	}

	private Vector3f computeIntersection(Vector3f a, Vector3f c){
		Vector3f n = (triangle.v2.subtract(triangle.v1)).cross(triangle.v3.subtract(triangle.v1));
		float D = n.scale(-1).dot(a),
			  t = - (n.dot(a) + D) / (n.dot(c.subtract(a)));
		return a.add(c.subtract(a).scale(t));
	}

	public float implicit(Vector3f p){
		return bMinACrossCMinA.dot(p.subtract(a));
	}
	
	private void swap(Vector3f a, Vector3f b){
		Vector3f temp = new Vector3f();
		temp.set(a);
		a.set(b);
		b.set(temp);
	}
	
	private void swap(float a, float b){
		float temp = 0f;
		temp = a;
		a = b;
		b = temp;
	}

	public void draw(Vector3f e, Rasterizer r) {
		if(implicit(e) < 0){
			if(positive != null)
				positive.draw(e, r);
			r.rasterizeTriangle(triangle);
			if(negative != null)
				negative.draw(e,r);
		}
		else{
			if(negative != null)
				negative.draw(e,r);
			r.rasterizeTriangle(triangle);
			if(positive != null)
				positive.draw(e,r);
		}
	}
}
